// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gpu/command_buffer/service/transfer_buffer_manager.h"

#include <limits>

#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/process/process_handle.h"
#include "base/strings/stringprintf.h"
#include "base/thread_task_runner_handle.h"
#include "base/trace_event/memory_allocator_dump.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/process_memory_dump.h"
#include "base/trace_event/trace_event.h"
#include "gpu/command_buffer/common/cmd_buffer_common.h"
#include "gpu/command_buffer/common/gles2_cmd_utils.h"
#include "gpu/command_buffer/service/memory_tracking.h"

using ::base::SharedMemory;

namespace gpu {

TransferBufferManagerInterface::~TransferBufferManagerInterface()
{
}

TransferBufferManager::TransferBufferManager(
    gles2::MemoryTracker* memory_tracker)
    : shared_memory_bytes_allocated_(0)
    , memory_tracker_(memory_tracker)
{
}

TransferBufferManager::~TransferBufferManager()
{
    while (!registered_buffers_.empty()) {
        BufferMap::iterator it = registered_buffers_.begin();
        DCHECK(shared_memory_bytes_allocated_ >= it->second->size());
        shared_memory_bytes_allocated_ -= it->second->size();
        registered_buffers_.erase(it);
    }
    DCHECK(!shared_memory_bytes_allocated_);

    base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
        this);
}

bool TransferBufferManager::Initialize()
{
    // When created from InProcessCommandBuffer, we won't have a |memory_tracker_|
    // so don't register a dump provider.
    if (memory_tracker_) {
        base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
            this, "TransferBufferManager::Initialize", base::ThreadTaskRunnerHandle::Get());
    }
    return true;
}

bool TransferBufferManager::RegisterTransferBuffer(
    int32 id,
    scoped_ptr<BufferBacking> buffer_backing)
{
    if (id <= 0) {
        DVLOG(0) << "Cannot register transfer buffer with non-positive ID.";
        return false;
    }

    // Fail if the ID is in use.
    if (registered_buffers_.find(id) != registered_buffers_.end()) {
        DVLOG(0) << "Buffer ID already in use.";
        return false;
    }

    // Register the shared memory with the ID.
    scoped_refptr<Buffer> buffer(new gpu::Buffer(buffer_backing.Pass()));

    // Check buffer alignment is sane.
    DCHECK(!(reinterpret_cast<uintptr_t>(buffer->memory()) & (kCommandBufferEntrySize - 1)));

    shared_memory_bytes_allocated_ += buffer->size();

    registered_buffers_[id] = buffer;

    return true;
}

void TransferBufferManager::DestroyTransferBuffer(int32 id)
{
    BufferMap::iterator it = registered_buffers_.find(id);
    if (it == registered_buffers_.end()) {
        DVLOG(0) << "Transfer buffer ID was not registered.";
        return;
    }

    DCHECK(shared_memory_bytes_allocated_ >= it->second->size());
    shared_memory_bytes_allocated_ -= it->second->size();

    registered_buffers_.erase(it);
}

scoped_refptr<Buffer> TransferBufferManager::GetTransferBuffer(int32 id)
{
    if (id == 0)
        return NULL;

    BufferMap::iterator it = registered_buffers_.find(id);
    if (it == registered_buffers_.end())
        return NULL;

    return it->second;
}

bool TransferBufferManager::OnMemoryDump(
    const base::trace_event::MemoryDumpArgs& args,
    base::trace_event::ProcessMemoryDump* pmd)
{
    for (const auto& buffer_entry : registered_buffers_) {
        int32 buffer_id = buffer_entry.first;
        const Buffer* buffer = buffer_entry.second.get();
        std::string dump_name = base::StringPrintf("gpu/transfer_memory/client_%d/buffer_%d",
            memory_tracker_->ClientId(), buffer_id);
        base::trace_event::MemoryAllocatorDump* dump = pmd->CreateAllocatorDump(dump_name);
        dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
            base::trace_event::MemoryAllocatorDump::kUnitsBytes,
            buffer->size());
        auto guid = GetBufferGUIDForTracing(memory_tracker_->ClientTracingId(), buffer_id);
        pmd->CreateSharedGlobalAllocatorDump(guid);
        pmd->AddOwnershipEdge(dump->guid(), guid);
    }

    return true;
}

} // namespace gpu
